package com.guigu.gulisearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.guigu.gulicommon.entity.pojo.AttrsPoJo;
import com.guigu.gulicommon.entity.pojo.res.Res;
import com.guigu.gulicommon.exception.BaseException;
import com.guigu.gulisearch.entity.ProductEs;
import com.guigu.gulisearch.entity.dto.ProductSearchDto;
import com.guigu.gulisearch.entity.vo.ProductSearchVo;
import com.guigu.gulisearch.mapper.ProductEsMapper;
import com.guigu.gulisearch.service.MallSearchService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
import org.dromara.easyes.core.toolkit.FieldUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.guigu.gulicommon.constant.enumes.res.OtherResCode.PARAMETER_TYPE_ERROR;
import static com.guigu.gulicommon.constant.enumes.res.SelectResCode.SEARCH_PRODUCT_SUCCESS;

/**
 * @author 陈俊杰
 * @version 1.0
 * @since 2024/9/10 00:16
 */
@Service
@Log4j2
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    private ProductEsMapper productEsMapper;

    /**
     * 商品检索
     *
     * @param dto 商品检索参数
     * @return 商品检索结果
     */
    @Override
    public Res<ProductSearchVo> search(ProductSearchDto dto) {
        //1.构建基本查询条件
        LambdaEsQueryWrapper<ProductEs> productEsLambdaEsQueryChainWrapper = new LambdaEsQueryWrapper<>();
        //1.1 关键字查询
        productEsLambdaEsQueryChainWrapper.and(i -> i.match(StringUtils.isNotBlank(dto.getQueryParam()), ProductEs::getSkuTitle, dto.getQueryParam()))
                //1.2三级分类id查询
                .filter(i -> i.eq(dto.getThirdCatId() != null, ProductEs::getCatalogId, dto.getThirdCatId()))
                //1.3 品牌id查询
                .filter(i -> i.in(dto.getBrandId() != null && !dto.getBrandId().isEmpty(), ProductEs::getBrandId, dto.getBrandId()))
                //1.4 库存状态查询
                .filter(i -> i.eq(dto.getHasStock() != null, ProductEs::getHasStock, dto.getHasStock()));
        try {
            //1.5 价格区间查询
            if (dto.getSkuPrice() != null) {
                //根据"_"切割获取价格区间
                String[] split = dto.getSkuPrice().split("_");
                //1.5.1 如果有两个元素，则进行区间查询
                if (split.length == 2) {
                    productEsLambdaEsQueryChainWrapper.filter(i -> i.between(ProductEs::getSkuPrice, split[0], split[1]));
                }
                //1.5.2 如果只有一个元素
                if (dto.getSkuPrice().startsWith("_")) {
                    //1.5.2.1 如果是以"_"开头，则表示小于该价格
                    productEsLambdaEsQueryChainWrapper.filter(i -> i.lt(ProductEs::getSkuPrice, split[0]));
                }
                if (dto.getSkuPrice().endsWith("_")) {
                    //1.5.2.2 如果以"_"结尾，则表示大于该价格
                    productEsLambdaEsQueryChainWrapper.filter(i -> i.gt(ProductEs::getSkuPrice, split[0]));
                }

            }
            //1.6 属性查询
            if (dto.getAttrs() != null && !dto.getAttrs().isEmpty()) {
                for (String attr : dto.getAttrs()) {
                    String[] split = attr.split("_");
                    //1.6.1 获取属性id
                    String attrId = split[0];
                    //1.6.2 获取属性值
                    List<String> attrValues = Arrays.asList(split[1].split(":"));
                    productEsLambdaEsQueryChainWrapper
                            .filter(i -> i.nested(ProductEs.getAttrsColumnName(), j -> j.eq(FieldUtils.val(AttrsPoJo::getAttrId), attrId).in(FieldUtils.val(AttrsPoJo::getAttrValue), attrValues)));
                }
            }
            //2 构建排序条件
            if (dto.getSort() != null) {
                String[] split = dto.getSort().getValue().split("_");
                String sortField = split[0];
                String sortType = split[1];
                if ("asc".equals(sortType)) {
                    productEsLambdaEsQueryChainWrapper.orderByAsc(sortField);
                } else {
                    productEsLambdaEsQueryChainWrapper.orderByDesc(sortField);
                }
            }
        } catch (Exception e) {
            throw new BaseException(PARAMETER_TYPE_ERROR);
        }
        //3构建聚合
        //这里由于Easy-ES不支持嵌套聚合，所以需要手动构建原生SearchSourceBuilder
        SearchSourceBuilder searchSourceBuilder = productEsMapper.getSearchSourceBuilder(productEsLambdaEsQueryChainWrapper);
        //3.1 构建品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50)
                //3.1.1 品牌名称子聚合
                .subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName")).size(50)
                //3.1.2 品牌图片子聚合
                .subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg")).size(50);
        //3.2 构建分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(50)
                .subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName")).size(50);
        //3.3.3 构建属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", ProductEs.getAttrsColumnName());
        attrAgg.subAggregation(
                //3.3.1 属性id聚合
                AggregationBuilders.terms("attr_id_agg").field("attrs.attrId")
                        //3.3.1.1 属性名称聚合
                        .subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName")).size(50)
                        //3.3.1.2 属性值聚合
                        .subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue")).size(50));
        //4.添加聚合
        searchSourceBuilder.aggregation(brandAgg).aggregation(catalogAgg).aggregation(attrAgg)
                .from((int) ((dto.getPageNum() - 1) * dto.getPageSize()))
                .size(Math.toIntExact(dto.getPageSize()));
        LambdaEsQueryWrapper<ProductEs> lambdaEsQueryWrapper = productEsLambdaEsQueryChainWrapper.setSearchSourceBuilder(searchSourceBuilder);
        SearchResponse search = productEsMapper.search(lambdaEsQueryWrapper);
        //5 封装结果
        return builderProductSearchVo(search, dto.getPageNum(), dto.getPageSize());
    }

    /**
     * 构建检索商品的返回结果
     *
     * @param search 检索商品信息
     * @return 检索商品的返回结果
     */
    private Res<ProductSearchVo> builderProductSearchVo(SearchResponse search, Long pageNum, Long pageSize) {


        ProductSearchVo productSearchVo = new ProductSearchVo();
        SearchHits hits = search.getHits();

        ArrayList<ProductEs> productEs = new ArrayList<>();
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits) {
                //1.获取商品信息
                ProductEs product = JSON.parseObject(hit.getSourceAsString(), ProductEs.class);
                //2 设置高亮
                if (hit.getHighlightFields().get("skuTitle") != null && hit.getHighlightFields().get("skuTitle").getFragments().length > 0) {
                    product.setSkuTitle(hit.getHighlightFields().get("skuTitle").getFragments()[0].toString());
                }
                productEs.add(product);
            }
        }
        productSearchVo.setProducts(productEs);
        //获取所有聚合
        Aggregations aggregations = search.getAggregations();
        //1.封装分类聚合
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> buckets = catalogAgg.getBuckets();
        ArrayList<ProductSearchVo.CategoryVo> categoryVos = new ArrayList<>();
        buckets.forEach(bucket -> {
            ProductSearchVo.CategoryVo categoryVo = new ProductSearchVo.CategoryVo();
            String keyAsString = bucket.getKeyAsString();
            //得到分类Id
            categoryVo.setCategoryId(Long.parseLong(keyAsString));
            //得到分类名称
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName;
            if (!catalogNameAgg.getBuckets().isEmpty()) {
                catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            } else {
                catalogName = "未知分类";
            }

            categoryVo.setCategoryName(catalogName);
            categoryVos.add(categoryVo);
        });
        //封装品牌聚合
        ParsedLongTerms brandAgg = aggregations.get("brand_agg");
        List<? extends Terms.Bucket> brandBuckets = brandAgg.getBuckets();
        ArrayList<ProductSearchVo.BrandVo> brandVos = new ArrayList<>();
        brandBuckets.forEach(bucket -> {
            ProductSearchVo.BrandVo brandVo = new ProductSearchVo.BrandVo();
            String keyAsString = bucket.getKeyAsString();
            //得到品牌Id
            brandVo.setBrandId(Long.parseLong(keyAsString));
            //得到品牌名称
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName;
            if (!brandNameAgg.getBuckets().isEmpty()) {
                brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            } else {
                brandName = "未知品牌";
            }

            brandVo.setBrandName(brandName);
            //得到品牌图片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg;
            if (!brandImgAgg.getBuckets().isEmpty()) {
                brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            } else {
                brandImg = null;
            }
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        });

        //封装属性聚合
        ArrayList<ProductSearchVo.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attrAgg = aggregations.get("attr_agg");
        //1.获取属性id聚合
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        attrIdAgg.getBuckets().forEach(attrIdBucket -> {
            ProductSearchVo.AttrVo attrVo = new ProductSearchVo.AttrVo();
            //1.1 得到属性id
            String attrIdKeyAsString = attrIdBucket.getKeyAsString();
            attrVo.setAttrId(Long.parseLong(attrIdKeyAsString));
            //1.2 得到属性名称
            ParsedStringTerms attrNameAgg = attrIdBucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //1.3 得到属性值
            ParsedStringTerms attrValueAgg = attrIdBucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attrValueAgg.getBuckets().stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            attrVo.setAttrValues(attrValues);
            attrVos.add(attrVo);
        });

        productSearchVo.setBrands(brandVos);
        productSearchVo.setCategories(categoryVos);
        productSearchVo.setAttrs(attrVos);
        ArrayList<ProductSearchVo> productSearchVos = new ArrayList<>();
        productSearchVos.add(productSearchVo);
        return new Res<>(SEARCH_PRODUCT_SUCCESS.getCode(), SEARCH_PRODUCT_SUCCESS.getMsg(), hits.getTotalHits().value, hits.getTotalHits().value, pageNum, pageSize, productSearchVos);
    }


}
